In [1]:
from random import choice, sample
from copy import deepcopy
from scipy.stats import bernoulli
from collections import Counter, defaultdict
from source.host import Host
from source.virus import Virus

import matplotlib.pyplot as plt
import numpy as np
import networkx as nx
import seaborn as sns

sns.set_style('white')
sns.set_context('talk')
np.random.seed(45) # for reproducibility

%load_ext autoreload
%autoreload 2
%matplotlib inline

Agent-Based Model to Dissect Contribution of Host Immunity and Contact Structure to Influenza Reassortment

Reassortment

  • The reticulate evolutionary mechanism for the influenza virus.
  • Implicated in all past human pandemics.
  • Quantitatively important for host switches. (we're really excited about this finding!)

Scientific Questions

  • How does host contact structure affect the ability of a virus to shuffle its genome?
  • How does host immunity affect the necessity of a virus to shuffle its genome?
  • How does fitness of the virus within a host affect the ability of the virus to reassort? @Wendy
  • How does waning immunity affect the necessity of the virus to reassort? @Jon

Agent-Based Simulation Setup

  • Two types of agents: Host and Virus.
  • Interaction between hosts:
    • Homophily: degree of preference for interaction with the same color.
    • Transmission: passing on of virus.
  • Interaction between viruses:
    • Reassortment: exchange of genes between viruses.
  • Interaction between host and virus:
    • Host can gain immunity to viruses over time.
    • Virus has host preference of same color on one segment.
  • Host:
    • colour
    • immunity
    • viruses
    • expiry_time
    • alive_time
  • Virus:
    • seg1color
    • seg2color
    • infection_time
    • expiry_time

Simulation Setup

In the following blocks of code, we will look at how the simulation will run.

Initialization

1000 hosts: 500 red, 500 blue.


In [2]:
hosts = []
n_hosts = 1000
for i in range(n_hosts):
    if i < n_hosts / 2:
        hosts.append(Host(color='blue'))
    else:
        hosts.append(Host(color='red'))

Initialization

Pick 5 red hosts, infect with one red virus each.

Pick 5 blue hosts, infect iwth one blue virus each.


In [3]:
# Pick 5 red hosts and 5 blue hosts at random, and infect it with a virus of the same color.
blue_hosts = [h for h in hosts if h.color == 'blue']
blue_hosts = sample(blue_hosts, 10)
blue_virus = Virus(seg1color='blue', seg2color='blue')
for h in blue_hosts:
    h.viruses.append(blue_virus)

red_hosts = [h for h in hosts if h.color == 'red']
red_hosts = sample(red_hosts, 10)
red_virus = Virus(seg1color='red', seg2color='red')
for h in red_hosts:
    h.viruses.append(red_virus)

Parameters


In [4]:
p_immune = 1E-3   # 1 = always successful even under immune pressure
                  # 0 = always unsuccessful under immune pressure.
p_replicate = 0.95   # probability of replication given that a host is infected.
p_contact   = 1 - 1E-1/n_hosts  # probability of contacting a host of the same color.
p_same_color = 0.99   # probability of successful infection given segment of same color.
p_diff_color = 0.9    # probability of successful infection given segment of different color.
 
# Set up number of timesteps to run simulation
n_timesteps = 100

# Set up a defaultdict for storing data
data = defaultdict(list)

Run Simulation!


In [5]:
# Run simulation
for t in range(n_timesteps):  
    # First part, clear up old infections.
    for h in hosts:
        h.increment_time()
        h.remove_old_viruses()
        h.remove_immune_viruses()
        
    # Step to replicate viruses present in hosts.
    infected_hosts = [h for h in hosts if h.is_infected()]
    for h in infected_hosts:
        if bernoulli.rvs(p_replicate): # we probabilistically allow replication to occur
            h.replicate_virus()
    
    # Step to transmit the viruses present in hosts.
    infected_hosts = [h for h in hosts if h.is_infected()]
    num_contacts = 0
    for h in infected_hosts:
        same_color = bernoulli.rvs(p_contact)
        if same_color:
            new_host = choice([h2 for h2 in hosts if h2.color == h.color])
            num_contacts += 0
        else:
            new_host = choice([h2 for h2 in hosts if h2.color != h.color])
            num_contacts += 1
        virus = h.viruses[-1] # choose the newly replicated virus every time.
        
        # Determine whether to transmit or not.
        p_transmit = 1
        ### First, check immunity ###
        if virus.seg1color in new_host.immunity:
            p_transmit = p_transmit * p_immune
        elif virus.seg1color not in new_host.immunity:
            pass
        
        ### Next, check seg1.
        if virus.seg1color == new_host.color:
            p_transmit = p_transmit * p_same_color
        else:
            p_transmit = p_transmit * p_diff_color
        
        ### Finally, check seg2.
        if virus.seg2color == new_host.color:
            p_transmit = p_transmit * p_same_color
        else:
            p_transmit = p_transmit * p_diff_color

        # Determine whether to transmit or not, by using a Bernoulli trial.
        transmit = bernoulli.rvs(p_transmit)
        
        # Perform transmission step
        if transmit:
            new_host.viruses.append(virus)
#             # Capture data in the summary graph.
#             if virus.is_mixed():
#                 G.edge[h.color][new_host.color]['mixed'] += 1
#             else:
#                 G.edge[h.color][new_host.color]['clonal'] += 1
        else:
            pass
        
        
    ### INSPECT THE SYSTEM AND RECORD DATA###
    
    num_immunes = 0  # num immune hosts
    num_infected = 0  # num infected hosts
    num_blue_immune = 0  # num blue immune hosts
    num_red_immune = 0  # num red immune hosts
    num_uninfected = 0  # num uninfected hosts
    num_mixed = 0  # num mixed viruses
    num_original = 0  # num original colour viruses
    num_red_virus = 0  # num red viruses
    num_blue_virus = 0  # num blue viruses
    
    for h in hosts:
        if len(h.immunity) > 0:
            num_immunes += 1
        if h.is_infected() > 0:
            num_infected += 1
        if 'blue' in h.immunity:
            num_blue_immune += 1
        if 'red' in h.immunity:
            num_red_immune += 1
        if not h.is_infected():
            num_uninfected += 1
            
        for v in h.viruses:
            if v.is_mixed():
                num_mixed += 1
            else:
                if v.seg1color == 'blue' and v.seg2color == 'blue':
                    num_blue_virus += 1
                elif v.seg1color == 'red' and v.seg2color == 'red':
                    num_red_virus += 1
                num_original += 1
    
    # Record data that was captured
    data['n_immune'].append(num_immunes)
    data['n_infected'].append(num_infected)
    data['n_blue_immune'].append(num_blue_immune)
    data['n_red_immune'].append(num_red_immune)
    data['n_uninfected'].append(num_uninfected)
    data['n_mixed'].append(num_mixed)
    data['n_original'].append(num_original)
    data['n_red_virus'].append(num_red_virus)
    data['n_blue_virus'].append(num_blue_virus)
    data['n_contacts'].append(num_contacts)
    ### INSPECT THE SYSTEM ###

Result: Viral Dynamics


In [6]:
# Reassortment successful in establishing infection or not?
plt.plot(data['n_red_virus'], color='red', label='red')
plt.plot(data['n_blue_virus'], color='blue', label='blue')
plt.plot(data['n_original'], color='black', label='original')
plt.plot(data['n_mixed'], color='purple', label='mixed')
plt.ylabel('Number of  Viruses')
plt.xlabel('Time Step')
plt.title('Viruses')
plt.legend()


Out[6]:
<matplotlib.legend.Legend at 0x10d880f98>

In [43]:
np.array_equal(np.array(data['n_mixed']), np.zeros(100))


Out[43]:
False

In [44]:
np.where(np.array(data['n_mixed']) == np.max(data['n_mixed']))[0] - np.where(np.array(data['n_original']) == np.max(data['n_original']))[0]


Out[44]:
array([14])

Result: Host Immunity


In [61]:
plt.plot(data['n_infected'], color='green', label='infected')
plt.plot(data['n_immune'], color='purple', label='immune')
plt.plot(data['n_blue_immune'], color='blue', label='blue immune')
plt.plot(data['n_red_immune'], color='red', label='red immune')
plt.ylabel('Number')
plt.xlabel('Time Steps')
plt.title('Hosts')
plt.legend()


Out[61]:
<matplotlib.legend.Legend at 0x10e5a64a8>

Result: Contact Frequency


In [62]:
plt.plot(data['n_contacts'], color='olive', label='contacts')
plt.title('Contact Frequency')
np.where(np.array(data['n_contacts']) >= 1)[0]


Out[62]:
array([14, 34, 35, 42])

In [47]:
import pandas as pd
pd.DataFrame(data)


Out[47]:
n_blue_immune n_blue_virus n_contacts n_immune n_infected n_mixed n_original n_red_immune n_red_virus n_uninfected
0 0 14 0 0 20 0 29 0 15 980
1 6 12 0 11 17 0 26 5 14 983
2 6 28 0 11 32 0 58 5 30 968
3 6 56 0 11 60 0 118 5 62 940
4 10 100 0 22 109 0 202 12 102 891
5 18 180 0 39 193 0 349 21 169 807
6 26 328 0 53 320 0 633 27 305 680
7 47 569 0 93 490 0 1086 46 517 510
8 80 880 0 162 644 0 1664 82 784 356
9 139 1180 1 276 760 0 2235 137 1055 240
10 236 1257 0 451 830 1 2522 215 1265 170
11 316 1249 1 620 824 0 2547 304 1298 176
12 383 1148 0 767 787 0 2330 384 1182 213
13 438 1000 0 887 741 2 1968 451 968 259
14 466 874 2 939 697 3 1682 477 808 303
15 483 772 0 969 667 7 1477 493 705 333
16 496 683 0 989 636 20 1312 506 629 364
17 502 641 1 991 623 47 1237 514 596 377
18 507 631 1 995 613 70 1208 525 577 387
19 517 613 0 999 623 157 1162 529 549 377
20 534 594 0 999 642 279 1097 547 503 358
21 556 613 0 999 679 428 1092 576 479 321
22 586 638 0 999 733 636 1108 607 470 267
23 624 668 0 1000 768 916 1103 656 435 232
24 673 725 0 1000 783 1063 1085 736 360 217
25 733 738 2 1000 801 1157 1045 799 307 199
26 804 737 0 1000 781 1134 991 865 254 219
27 857 706 2 1000 755 1081 901 908 195 245
28 908 636 0 1000 715 1001 795 942 159 285
29 951 555 0 1000 683 904 688 963 133 317
... ... ... ... ... ... ... ... ... ... ...
70 1000 61 0 1000 86 91 79 1000 18 914
71 1000 59 0 1000 84 88 77 1000 18 916
72 1000 58 0 1000 81 85 76 1000 18 919
73 1000 57 0 1000 80 82 75 1000 18 920
74 1000 53 0 1000 77 80 70 1000 17 923
75 1000 48 0 1000 73 80 64 1000 16 927
76 1000 42 0 1000 71 78 57 1000 15 929
77 1000 37 0 1000 64 75 51 1000 14 936
78 1000 35 0 1000 62 72 48 1000 13 938
79 1000 34 0 1000 58 66 46 1000 12 942
80 1000 34 0 1000 54 62 46 1000 12 946
81 1000 33 0 1000 54 58 45 1000 12 946
82 1000 31 0 1000 50 52 43 1000 12 950
83 1000 29 0 1000 45 47 41 1000 12 955
84 1000 28 0 1000 43 44 40 1000 12 957
85 1000 27 0 1000 41 42 39 1000 12 959
86 1000 26 0 1000 40 41 38 1000 12 960
87 1000 26 0 1000 39 40 38 1000 12 961
88 1000 25 0 1000 39 39 37 1000 12 961
89 1000 23 0 1000 37 37 35 1000 12 963
90 1000 22 0 1000 35 36 34 1000 12 965
91 1000 22 0 1000 35 34 33 1000 11 965
92 1000 22 0 1000 32 32 32 1000 10 968
93 1000 20 0 1000 32 32 30 1000 10 968
94 1000 18 0 1000 30 30 28 1000 10 970
95 1000 18 0 1000 28 28 28 1000 10 972
96 1000 16 0 1000 28 27 26 1000 10 972
97 1000 13 0 1000 25 26 23 1000 10 975
98 1000 12 0 1000 24 26 22 1000 10 976
99 1000 11 0 1000 24 26 20 1000 9 976

100 rows × 10 columns

Future Work

  1. Code optimizations for 10-100X speedups in running simulations.
    1. Parallelization
    2. Cython or numba
  2. Other metrics to measure beyond population immunity:
    1. Total duration of mixed virus emergence.
  3. What's the parameter space where unusual things happen?

In [ ]: